// Reactor 反应堆模式服务器  -- 基于 Tcp , 用到 epoll 的 ET模式

// 防止头文件被重复包含

#ifndef __REATER__HPP__
#define __REATER__HPP__

// 该层为最底层 , 只负责为上层提供基座方法 , 什么都不干
// 该层只负责管理连接(Connect)管理获取的 fd , 派发对应任务
// 其实就是基于tcp , 只不过加上了设计模式 , Reator 反应堆模式

#include <iostream>
#include <memory>
#include <unordered_map>
#include "Epoller.hpp"
#include "Connect.hpp"

class Reactor
{
    static const int revs_num = 128;

private:
    // 帮助函数 , 防止冗余
    bool IsConnectionExistsHelper(int sockfd)
    {
        // 查找
        auto iter = _connects.find(sockfd);
        if (iter == _connects.end())
            return false;
        else
            return true;
    }

    // 根据 Connect中的 Getfd  直接查
    bool IsConnectionExists(const std::shared_ptr<Connect> &conn)
    {
        return IsConnectionExistsHelper(conn->GetSockFd());
    }

    // 根据 fd 直接查
    bool IsConnectionExists(int sockfd)
    {
        return IsConnectionExistsHelper(sockfd);
    }

    // 1. 先等 , 等到了派发
    int LoopOnce(int timeout)
    {
        return _epoll_prt->WaitEvents(_revs, revs_num, timeout);
    }

    // 2 . 事件派发器
    void Dispatcher(int n)
    {
        for (int i = 0; i < n; ++i)
        {
            // 获取就绪的 fd 和 就绪的事件
            int sockfd = _revs[i].data.fd;
            uint32_t revents = _revs[i].events;

            // 1. 获取所有事件 , 进行异常处理 , 都转成 IO
            if (revents & EPOLLERR)
                revents |= (EPOLLIN | EPOLLOUT); // 将所有的异常处理，统一转化成IO错误 ,IO中就会处理异常
            if (revents & EPOLLHUP)
                revents |= (EPOLLIN | EPOLLOUT); // 将所有的异常处理，统一转化成IO错误 ,IO中就会处理异常

            ///// IO

            // 2 . 看事件是写 , 还是读事件 --- 读事件就绪
            if (revents & EPOLLIN)
            {
                if (IsConnectionExists(sockfd))
                    _connects[sockfd]->Recver();
            }
            // --- 写事件就绪
            if (revents & EPOLLOUT)
            {

                if (IsConnectionExists(sockfd))
                    _connects[sockfd]->Sender();
            }
        }
    }

    bool IsConnectionEmpty()
    {
        return _connects.empty();
    }

public:
    Reactor()
        : _isrunning(false), _epoll_prt(std::make_unique<Epoll>())
    {
    }
    ~Reactor() {}

    // 服务器是死循环  - Loop 循环
    // 循环干什么 ?? 获取fd , 派发事件
    void Loop()
    {
        // 有fd了 , 在派发等 ...
        if (IsConnectionEmpty())
            return;

        _isrunning = true;

        // 基于epoll 模型的 , 所以 epoll 先等 , 有了通知我 fd
        while (_isrunning)
        {
            // 1. 获取就绪的 fd 事件
            int timeout = -1; // -1阻塞等 , 1就是非阻塞
            int n = LoopOnce(timeout); // 得到了就绪fd个数
            // 2. 通知上层 -- 派发
            Dispatcher(n);
        }

        _isrunning = false;
    }

    // 添加关心
    void EnableReadWrite(int sockfd, bool enableread, bool enablewrite)
    {
        // 0. 不要重复添加
        if (!IsConnectionExists(sockfd))
        {
            LOG(LogLevel::WARNING) << "EnableReadWrite, conn is exists: " << sockfd;
            return;
        }

        // 1. 修改当前sockfd对应的connection关心的事件
        uint32_t new_event = (EPOLLET | (enableread ? EPOLLIN : 0) | (enablewrite ? EPOLLOUT : 0));
        _connects[sockfd]->SetEvent(new_event);

        // 2. 写透到内核，调整sockfd对特定事件的关心
        _epoll_prt->ModifyEvent(sockfd, new_event);
    }

    //*************  管理连接模块(管理fd) *************
    void AddConnect(std::shared_ptr<Connect> &conn)
    {
        if (IsConnectionExists(conn))
        {
            LOG(LogLevel::WARNING) << "conn is existed : " << conn->GetSockFd();
            return;
        }

        // 添加 Connect : 1 . 添加对 fd 事件的关心 2 . 写透到内核  3 . 添加到 _connects 管理列表中

        // 1 . 添加对事件的关心 ? 谁关心事件 ?  Epoll
        // 获取 fd 和 关心事件
        int sockfd = conn->GetSockFd();
        uint32_t event = conn->GetEvents();
        // 写透到内核
        _epoll_prt->AddEvent(sockfd, event);

        // 2 . 设置回指指针 , 因为添加的是一个新的 Connect , 也要指向我
        conn->SetOwner(this);
        // 3 . 添加到管理列表
        _connects[sockfd] = conn;
    }

    // 因为用的是哈希 , fd : Connect , 所以一个 fd 可以找到一个 Connect
    void DelConnect(int sockfd)
    {
        // 内核中删除
        _epoll_prt->DelEvent(sockfd);

        // 管理列表中删除
        _connects.erase(sockfd);

        // 关闭不用的fd -- 必须最后关闭 , 因为 epoll 删除必须是合法的 fd
        close(sockfd);
    }

    //*************  管理连接模块(管理fd) *************

    void Stop()
    {
        _isrunning = false;
    }

private:
    // 1 . 有 epoll 模型的 ET 模式 , 构建epoll 模型
    std::unique_ptr<Epoll> _epoll_prt; // 只负责IO等
    // 2 . 哈希 , 因为我们用的是 Connect基类 , 不知道是哪个fd , 通过fd可以找到对应连接
    // 所以 , 基类指针可以指向不同的子类 , 可以是 listenfd , 可以是普通 fd
    // 管理所有获取的 fd
    std::unordered_map<int, std::shared_ptr<Connect>> _connects;

    // 3. 启动
    bool _isrunning;

    // 4. 就绪事件
    struct epoll_event _revs[revs_num];
};

#endif